/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.box.asm

import cool.taomu.box.asm.entity.ClassEntity
import cool.taomu.box.asm.entity.FieldEntity
import cool.taomu.box.asm.entity.MethodEntity
import java.io.PrintWriter
import java.lang.reflect.Constructor
import java.util.LinkedList
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.GeneratorAdapter
import org.objectweb.asm.commons.Method
import org.objectweb.asm.util.TraceClassVisitor
import org.slf4j.LoggerFactory

class CreateClass extends ClassLoader {
	val static LOG = LoggerFactory.getLogger(CreateClass);
	ClassWriter cwriter;
	ClassVisitor tcvisitor;
	GeneratorAdapter ga;
	ClassEntity classEntity;

	new(ClassEntity classEntity, int version, int access) {
		this(classEntity, version, access, false);
	}

	new(ClassEntity classEntity, int version, int access, boolean isDebug) {
		cwriter = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
		if (isDebug) {
			tcvisitor = new TraceClassVisitor(cwriter, new PrintWriter(System.err))
		} else {
			tcvisitor = cwriter;
		}
		this.classEntity = classEntity;
		this.classEntity.cwriter = cwriter;
		tcvisitor.visit(version, access, classEntity.fullName, null, classEntity.superclass, classEntity.interfaces);
	}

	def static Class(ClassEntity classEntity) {
		return new CreateClass(classEntity, classEntity.version, Opcodes.ACC_PUBLIC, false);
	}

	def static Class(ClassEntity classEntity, boolean isDebug) {
		return new CreateClass(classEntity, classEntity.version, Opcodes.ACC_PUBLIC, isDebug);
	}

	def static Class(ClassEntity classEntity, int version, boolean isDebug) {
		return new CreateClass(classEntity, version, Opcodes.ACC_PUBLIC, isDebug);
	}

	def static Class(ClassEntity classEntity, int version) {
		return new CreateClass(classEntity, version, Opcodes.ACC_PUBLIC);
	}

	def static Interface(ClassEntity classEntity, int version) {
		return new CreateClass(classEntity, version, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE);
	}

	def static Abstract(ClassEntity classEntity, int version) {
		return new CreateClass(classEntity, version, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT);
	}

	def field(FieldEntity field) {
		tcvisitor.visitField(field.access, field.name, field.descriptor, null, field.value).visitEnd();
	}

	def constructor(Constructor<?>[] constructor) {
		constructor.filterNull.forEach [
			var m = Method.getMethod(it);
			var ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, null, tcvisitor);
			ga.loadThis();
			if (it.parameters.size > 0) {
				ga.loadArgs(0, it.parameters.size - 1);
			}
			ga.invokeConstructor(Type.getObjectType(classEntity.superclass), m);
			ga.returnValue();
			ga.endMethod();
		]
		return this;
	}

	def method(MethodEntity method) {
		ga = new GeneratorAdapter(method.access, Method.getMethod(method.defineMethod, method.defaultPackage), null,
			null, tcvisitor);
		return new CreateMethod(this.classEntity, ga);
	}

	def end() {
		tcvisitor.visitEnd();
		/*var bytes = toByteArray();
		 * var temp = File.createTempFile(this.classEntity.className, ".class");
		 * FileUtils.writeByteArrayToFile(temp, bytes);
		 * 
		 */
		return this;
	}

	def toByteArray() {
		return cwriter.toByteArray();
	}

	override findClass(String name) {
		var bytes = toByteArray();
		LOG.info("执行findClass:{}", name);
		return super.defineClass(name, bytes, 0, bytes.length);
	}
}
